/*
 * Copyright (C) 2018 Logos Technology Ltd.
 */

package com.logos.tool.rubberstamp;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Pair;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class RubberStamp {

    private Context mContext;
    private static final int BACKGROUND_MARGIN = 10;

    public RubberStamp(@NonNull Context context) {
        mContext = context;
    }

    /**
     * add stamp
     *
     * @param config RubberStampConfig
     * @return bitmap
     */
    public Bitmap addStamp(@NonNull RubberStampConfig config) {
        if (config == null) {
            throw new IllegalArgumentException("The config passed to this method should never"
                    + "be null");
        }
        Bitmap baseBitmap = getBaseBitmap(config);
        if (baseBitmap == null) {
            return baseBitmap;
        }

        int baseBitmapWidth = baseBitmap.getWidth();
        int baseBitmapHeight = baseBitmap.getHeight();

        Bitmap result = Bitmap.createBitmap(baseBitmapWidth, baseBitmapHeight,
                baseBitmap.getConfig());
        Canvas canvas = new Canvas(result);
        canvas.drawBitmap(baseBitmap, 0, 0, null);

        // Either one of the methods(text/bitmap) can be used to add a rubberstamp
        if (!TextUtils.isEmpty(config.getRubberStampString())) {
            addTextToBitmap(config, canvas, baseBitmapWidth, baseBitmapHeight);
        } else if (config.getRubberStampBitmap() != null) {
            addBitmapToBitmap(config.getRubberStampBitmap(), config, canvas,
                    baseBitmapWidth, baseBitmapHeight);
        }
        return result;
    }

    @Nullable
    private Bitmap getBaseBitmap(@NonNull RubberStampConfig config) {
        Bitmap baseBitmap = config.getBaseBitmap();
        @DrawableRes int drawable = config.getBaseDrawable();

        if (baseBitmap == null) {
            baseBitmap = BitmapFactory.decodeResource(mContext.getResources(), drawable);
            if (baseBitmap == null) {
                return null;
            }
        }
        return baseBitmap;
    }

    /**
     * Method to add text RubberStamp to a canvas based on the provided configuration
     *
     * @param config           The RubberStampConfig that specifies how the RubberStamp should look
     * @param canvas           The canvas on top of which the RubberStamp needs to be drawn
     * @param baseBitmapWidth  The width of the base bitmap
     * @param baseBitmapHeight The height of the base bitmap
     */
    private void addTextToBitmap(@NonNull RubberStampConfig config,
                                 @NonNull Canvas canvas,
                                 int baseBitmapWidth,
                                 int baseBitmapHeight) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setUnderlineText(false);

        paint.setTextSize(config.getTextSize());

        String typeFacePath = config.getTypeFacePath();
        // Add font typeface if its present in the config.
        if (!TextUtils.isEmpty(typeFacePath)) {
            Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), typeFacePath);
            paint.setTypeface(typeface);
        }

        Shader shader = config.getTextShader();
        // Add shader if its present in the config.
        if (shader != null) {
            paint.setShader(shader);
        }

        if (config.getTextShadowXOffset() != 0 || config.getTextShadowYOffset() != 0
                || config.getTextShadowBlurRadius() != 0) {
            // If any shadow property is present, set a shadow layer.
            paint.setShadowLayer(config.getTextShadowBlurRadius(),
                    config.getTextShadowXOffset(),
                    config.getTextShadowYOffset(),
                    config.getTextShadowColor());
        }

        Rect bounds = new Rect();

        String rubberStampString = config.getRubberStampString();
        paint.getTextBounds(rubberStampString, 0, rubberStampString.length(), bounds);

        int rubberStampWidth = bounds.width();
        float rubberStampMeasuredWidth = paint.measureText(rubberStampString);
        int rubberStampHeight = bounds.height();

        int positionX = config.getPositionX();
        int positionY = config.getPositionY();

        if (config.getRubberStampPosition() != RubberStampPosition.CUSTOM) {
            // If the specified RubberStampPosition is not CUSTOM, use calculates its x & y
            // co-ordinates.
            Pair<Integer, Integer> pair = PositionCalculator
                    .getCoordinates(config.getRubberStampPosition(),
                            baseBitmapWidth, baseBitmapHeight,
                            rubberStampWidth, rubberStampHeight);
            positionX = pair.first;
            positionY = pair.second;
        }

        // Add the margin to this position if it was passed to the config.
        positionX += config.getXMargin();
        positionY += config.getYMargin();

        float rotation = config.getRotation();
        // Add rotation if its present in the config.
        if (rotation != 0.0f) {
            canvas.rotate(rotation, positionX + bounds.exactCenterX(),
                    positionY - bounds.exactCenterY());
        }

        paint.setColor(config.getTextColor());

        int alpha = config.getAplha();
        // Add alpha to the rubberstamp if its within range or it uses the default value.
        if (alpha >= 0 && alpha <= 255) {
            paint.setAlpha(alpha);
        }

        if (config.getRubberStampPosition() != RubberStampPosition.TILE) {
            // The textBackgroundColor is only used if the specified RubberStampPosition is not TILE
            // This is because the background is actually a rectangle whose bounds are calcualted
            // below. In the case of TILE, we make use of a bitmap shader and there was no easy way
            // to draw the background rectangle for each tiled rubberstamp.
            int backgroundColor = config.getTextBackgroundColor();
            if (backgroundColor != 0) {
                Paint backgroundPaint = new Paint();
                backgroundPaint.setColor(backgroundColor);
                canvas.drawRect(positionX - BACKGROUND_MARGIN,
                        positionY - bounds.height()
                                - paint.getFontMetrics().descent - BACKGROUND_MARGIN,
                        (positionX + rubberStampMeasuredWidth
                                + config.getTextShadowXOffset() + BACKGROUND_MARGIN),
                        positionY + config.getTextShadowYOffset()
                                + paint.getFontMetrics().descent + BACKGROUND_MARGIN,
                        backgroundPaint);
            }
            StaticLayout layout = new StaticLayout(rubberStampString, new TextPaint(paint),
                    config.getBaseBitmap().getWidth() - 2 * positionX, Layout.Alignment.ALIGN_NORMAL,
                    (float) 1.0, (float) 0.0, true);
            canvas.translate(positionX, positionY - layout.getHeight());
            layout.draw(canvas);
        } else {
            // If the specified RubberStampPosition is TILE, it tiles the rubberstamp across
            // the bitmap. In order to generate a tiled bitamp, it uses a bitmap shader.
            Bitmap textImage = Bitmap.createBitmap((int) rubberStampMeasuredWidth,
                    rubberStampHeight,
                    Bitmap.Config.ARGB_8888);
            Canvas textCanvas = new Canvas(textImage);
            textCanvas.drawText(config.getRubberStampString(), 0, rubberStampHeight, paint);
            paint.setShader(new BitmapShader(textImage,
                    Shader.TileMode.REPEAT,
                    Shader.TileMode.REPEAT));
            Rect bitmapShaderRect = canvas.getClipBounds();
            canvas.drawRect(bitmapShaderRect, paint);
        }
    }

    /**
     * Method to add a bitmap RubberStamp to a canvas based on the provided configuration
     *
     * @param rubberStampBitmap The bitmap which will be used as the RubberStamp
     * @param config            The RubberStampConfig that specifies how the RubberStamp should look
     * @param canvas            The canvas on top of which the RubberStamp needs to be drawn
     * @param baseBitmapWidth   The width of the base bitmap
     * @param baseBitmapHeight  The height of the base bitmap
     */
    private void addBitmapToBitmap(@NonNull Bitmap rubberStampBitmap,
                                   @NonNull RubberStampConfig config,
                                   @NonNull Canvas canvas,
                                   int baseBitmapWidth,
                                   int baseBitmapHeight) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setUnderlineText(false);

        int alpha = config.getAplha();
        // Add alpha to the rubberstamp if its within range or it uses the default value.
        if (alpha >= 0 && alpha <= 255) {
            paint.setAlpha(alpha);
        }

        int positionX = config.getPositionX();
        int positionY = config.getPositionY();
        RubberStampPosition rubberStampPosition = config.getRubberStampPosition();
        if (rubberStampPosition != RubberStampPosition.CUSTOM) {
            // If the specified RubberStampPosition is not CUSTOM, use calculates its x & y
            // co-ordinates.
            Pair<Integer, Integer> pair =
                    PositionCalculator.getCoordinates(rubberStampPosition,
                            baseBitmapWidth, baseBitmapHeight,
                            rubberStampBitmap.getWidth(), rubberStampBitmap.getHeight());
            positionX = pair.first;
            positionY = pair.second - rubberStampBitmap.getHeight();
        }

        // Add the margin to this position if it was passed to the config.
        positionX += config.getXMargin();
        positionY += config.getYMargin();

        float rotation = config.getRotation();
        if (rotation != 0.0f) {
            // Add rotation if its present in the config.
            canvas.rotate(rotation, positionX + (rubberStampBitmap.getWidth() / 2),
                    positionY + (rubberStampBitmap.getHeight() / 2));
        }

        if (rubberStampPosition != RubberStampPosition.TILE) {
            canvas.drawBitmap(rubberStampBitmap, positionX, positionY, paint);
        } else {
            // If the specified RubberStampPosition is TILE, it tiles the rubberstamp across
            // the bitmap. In order to generate a tiled bitamp, it uses a bitmap shader.
            paint.setShader(new BitmapShader(rubberStampBitmap,
                    Shader.TileMode.REPEAT,
                    Shader.TileMode.REPEAT));
            Rect bitmapShaderRect = canvas.getClipBounds();
            canvas.drawRect(bitmapShaderRect, paint);
        }
    }
}
